/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Google Wave Add-on for Firefox.
 * The Initial Developer of the Original Code is Chad Smith.
 * Portions created by the Initial Developer are Copyright (C) 2008
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Mark Davies
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */

blipbalm = function(){
	return {
		appName:'Blipbalm',
		versionID:'1.0.0',
		Auth:null,
		load: function() {
			blipbalm.initChecker();
		},
		unload: function() {
			blipbalm.delegateNewMaster();
			blipbalm = null;
		},
		httpReq: null,
		intervalId: null,
		timeoutId: null,
		gwStatusBarId: 'blipbalm-statusbar',
		gwStatusBar: null,
		tooltipLabels: null,
		tooltipLabelCount: 0,
		tooltipElements: null,
		tooltipElementCount: 0,
		prefBranch: Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('extensions.blipbalm.'),
		JSON: Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON),
		passwordSites: ['chrome://blipbalm'],
		iconUnread: 'chrome://blipbalm/content/wavefave-unreadanim.gif', // waves with unread blip(s)
		iconOut: 'chrome://blipbalm/content/wavefave-out.png', // not logged in
		iconLoad: 'chrome://blipbalm/content/wavefave-loadanim.gif', // loading
		iconRead: 'chrome://blipbalm/content/wavefave.png', // logged in, no waves with unread blips
		iconError: 'chrome://blipbalm/content/wavefave-error.png', // error
		loginURL: 'https://www.google.com/accounts/ClientLogin', // same for Google accounts and hosted (Google Apps) accounts
		inboxURL: 'about:blank',
		logoutURL: 'about:blank',
		inboxGoogleURL: 'https://wave.google.com/wave/', // inbox URL for normal Google accounts
		logoutGoogleURL: 'https://wave.google.com/wave/logout', // logout URL for Google accounts
		inboxHostedURL: 'https://wave.google.com/a/{domain}/', // inbox URL for hosted accounts (Google Apps)
		logoutHostedURL: 'https://wave.google.com/a/{domain}/logout', // logout URL for hosted accounts
		aboutURL: 'http://code.google.com/p/blipbalm/',
		usernameFormField: 'Email',
		passwordFormField: 'Passwd',
		pwdmgr: null,
		pwdmgrInternal: null,
		oldUsername: null,
		defaultAccountType: 'GOOGLE',
		defaultFrequency: 5,
		minFrequency: 5,
		defaultHttpTimeout: 30,
		lastUpdated: null,
		checkingInProgress: false,
		
		finishedChecking: function() {
			this.debug('finished checking');
			this.checkingInProgress = false;
		},
		
		initChecker: function() {
			this.debug('checking init');
			try {
				this.gwStatusBar = document.getElementById(this.gwStatusBarId);
				this.initOption();
				var loggedIn = false,
					windowManager = this.getWindowManager(),
					enumerator = windowManager.getXULWindowEnumerator(null);
				while(enumerator.hasMoreElements()){
					var
						xulWindow = enumerator.getNext(),
						win = this.getDOMWindow(xulWindow);
					if (blipbalm != null && blipbalm.intervalId != null) {
						this.debug(win + ' intervalId is not null');
						loggedIn = false;
						if (this.gwStatusBar != null){
							var
								myBar = this.gwStatusBar,
								oldBar = xulWindow.docShell.contentViewer.DOMDocument.getElementById(this.gwStatusBarId);
							this.debug('copying status bar');
							myBar.src=oldBar.src;
							myBar.setAttribute('label', oldBar.getAttribute('label'));
							myBar.setAttribute('height', oldBar.getAttribute('height'));
							myBar.setAttribute('style', oldBar.getAttribute('style'));
							if (oldBar.getAttribute('tooltiptext')){
								this.debug('copying tooltiptext');
								myBar.setAttribute('tooltiptext',oldBar.getAttribute('tooltiptext'))
							}
							else{
								this.debug('copying tooltip');
								if(myBar.hasAttribute('tooltiptext'))
									myBar.removeAttribute('tooltiptext');
								var
									tooltipId = oldBar.getAttribute('tooltip'),
									oldTooltip = xulWindow.docShell.contentViewer.DOMDocument.getElementById(tooltipId),
									tooltip = this.newTooltip(document,myBar),
									i;
								for(i = 0; i < oldTooltip.childNodes.length; i++)
									tooltip.appendChild(this.cloneTooltipLabel(document,oldTooltip.childNodes.item(i)));
								document.getElementById('mainPopupSet').appendChild(tooltip);
								myBar.setAttribute('tooltip',tooltip.getAttribute('id'));
							}
						}
					}
				}
				if(document.getElementById(this.gwStatusBarId) && !loggedIn)
					this.start();
			}
			catch(e) {
				this.warn(e);
			}
		},
		
		delegateNewMaster: function() {
			try{
				this.debug('trying to delegate new master');
				if(this.intervalId != null){
					var
						newMasterFound = false,
						windowManager = this.getWindowManager(),
						enumerator = windowManager.getXULWindowEnumerator(null);
					while (enumerator.hasMoreElements() && !newMasterFound) {
						var
							xulWindow = enumerator.getNext(),
							win = this.getDOMWindow(xulWindow);
						if (blipbalm != null && blipbalm.intervalId == null) {
							this.debug('found new master ' + win);
							blipbalm.scheduleInboxChecking(win);
							newMasterFound = true;
						}
					}
				}
			}
			catch(e) {
				this.warn(e);
			}
		},
		
		login: function() {
			try {
				this.debug('trying to login');
				this.setStatusBarAttribute('src', this.iconLoad);
				var
					loginData = this.getLoginData(),
					accType = this.getAccountType(),
					username = loginData.username,
					pass = loginData.password,
					postBody;
				if (username && pass) {
					// set inboxURL and logoutURL depending on whether this is a GOOGLE or HOSTED account type
					if (accType == 'HOSTED') {
						this.inboxURL = this.useDomainFromUsername(this.inboxHostedURL, username);
						this.logoutURL = this.useDomainFromUsername(this.logoutHostedURL, username);
					} else {
						this.inboxURL = this.inboxGoogleURL;
						this.logoutURL = this.logoutGoogleURL;
					}
					this.httpReq = new XMLHttpRequest();
					this.httpReq.open('POST', this.loginURL, true);
					this.httpReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
					this.setReqTimeout(this.loginURL);
					var
						module = this;
					this.httpReq.onload = function() {
						module.parseLogin();
					};
					postBody = 'accountType=' + encodeURIComponent(accType) + 
							'&' + this.usernameFormField + '=' + encodeURIComponent(username) + 
							'&' + this.passwordFormField + '=' + encodeURIComponent(pass) + 
							'&service=wave&source=' + this.appName + '-' + this.versionID;
					//this.debug(postBody);
					this.httpReq.send(postBody);
					return true;
				} else {
					this.finishedChecking();
					var
						missing = [];
					if (username == null || username.length == 0)
						missing.push('email');
					if (pass == null || pass.length == 0)
						missing.push('password');
					this.setStatusBarAttribute('label', missing.join(' and ') + ' missing from settings');
					this.setStatusBarAttribute('height', 1);
					this.setStatusBarAttribute('src', this.iconError);
					return false;
				}
			} catch(e) {
				this.finishedChecking();
				this.warn(e);
				return false;
			}
		},
		
		parseLogin: function() {
			this.debug('parsing login');
			try {
				this.clearReqTimeout();
				var
					accType = this.getAccountType(),
					responseText = this.httpReq.responseText,
					error = responseText.match(/Error=([A-z]+)/),
					auth = responseText.match(/Auth=([A-z0-9_-]+)/);
				//this.debug('login response: ' + responseText);
				if (responseText.indexOf('Email:') > 0 || error) {
					this.finishedChecking();
					this.setStatusBarAttribute('label', error ? error[1] : 'incorrect login or password');
					this.setStatusBarAttribute('height', 1);
					this.setStatusBarAttribute('src', this.iconError);
				} else {
					if (auth) {
						blipbalm.Auth = auth[1];
						this.debug('successfully logged in');
						this.readInbox();
					}
				}
			} catch(e) {
				this.finishedChecking();
				this.warn(e);
			}
		},
		
		readInbox: function() {
			try{
				if (!blipbalm.Auth)
					this.login();
				this.debug('retrieving inbox');
				this.httpReq = new XMLHttpRequest();
				var inboxCheckURL = this.inboxURL + '?nouacheck&auth=' + encodeURIComponent(blipbalm.Auth);
				//this.debug(inboxCheckURL);
				this.httpReq.open('GET', inboxCheckURL, true);
				this.httpReq.setRequestHeader('User-Agent','Blipbalm for Firefox');
				this.setReqTimeout(this.inboxURL);
				var
					module = this;
				this.httpReq.onload = function() {
					module.parseInbox();
				};
				this.httpReq.send(null);
			} catch(e) {
				this.finishedChecking();
				this.warn(e);
			}
		},
		
		parseInbox: function(evt) {
			try {
				this.debug('parsing inbox');
				this.clearReqTimeout();
				if (!blipbalm.Auth) {
					this.login();
					return;
				}
				this.setLastUpdatedToNow();
				this.parseResponseText(this.httpReq.responseText);
				this.finishedChecking();
			} catch(e) {
				this.finishedChecking();
				this.warn(e);
			}
		},
		
		parseResponseText: function(responseText) {
			//this.debug('data to parse: ' + responseText);
			var data = responseText.match(/var json = (\{"r":"\^d1".*});/);
			if (!data) {
				this.finishedChecking();
				this.setStatusBarAttribute('src', this.iconError);
				this.setStatusBarAttribute('label', 'Cookies Blocked');
				return this.error('Add an exception for wave.google.com');
			}
			var 
				json = blipbalm.JSON.decode(data[1]),
				inbox = json.p[1],
				messages = [],
				unread = 0,
				readme,
				n;
			for (n in inbox) {
				if (readme = parseInt(inbox[n][7])) {
					messages.push(inbox[n][9][1]);
					unread++;
				}
			}
			this.setStatusBarAttribute('label', unread ? unread : '0');
			this.setStatusBarAttribute('height', 1);
			this.setStatusBarAttribute('src', unread ? this.iconUnread : this.iconRead);
			this.tooltipLabels = [];
			this.tooltipLabelCount = 0;
			this.tooltipElements = [];
			this.tooltipElementCount = 0;
			for (n in messages)
				this.addTooltipLabel(messages[n]);
			this.addTooltipLabel('Updated: ' + this.getDate(), 'color:#aaa');
			this.createTooltipElement(this.tooltipLabels);
			this.setStatusBarTooltip(this.tooltipElements);
		},
		
		checkWithLogout: function() {
			if (this.checkingInProgress) {
				this.debug('another check is still in progress');
				return;
			}
			this.checkingInProgress = true;
			try {
				this.debug('logging out');
				this.setStatusBarAttribute('src', this.iconLoad);
				this.httpReq=new XMLHttpRequest();
				this.httpReq.open('GET', this.logoutURL + '?auth=' + encodeURIComponent(blipbalm.Auth), true);
				this.setReqTimeout(this.logoutURL);
				var
					module = this;
				this.httpReq.onload = function() {
					module.afterLogout();
				};
				this.httpReq.send(null);
			} catch(e) {
				this.finishedChecking();
				this.warn(e);
			}
		},
		
		afterLogout: function() {
			this.debug('logging out finished');
			this.setStatusBarAttribute('src', this.iconOut);
			this.clearReqTimeout();
			this.login();
		},
		
		startInboxChecking: function() {
			this.debug('starting inbox checking');
			if(!blipbalm.Auth)
				this.login();
			this.scheduleInboxChecking(window);
		},
		
		scheduleInboxChecking: function(win) {
			if (blipbalm != null)
				blipbalm.intervalId = win.setInterval('blipbalm.readInbox()', this.getUpdateFrequency());
		},
		
		stopInboxChecking: function() {
			this.debug('stopping inbox checking');
			var
				windowManager = this.getWindowManager(),
				enumerator = windowManager.getXULWindowEnumerator(null);
			while (enumerator.hasMoreElements()) {
				var
					xulWindow = enumerator.getNext(),
					win = this.getDOMWindow(xulWindow);
				if (blipbalm != null && blipbalm.intervalId != null) {
					this.debug('cleaning old scheduled task');
					win.clearInterval(blipbalm.intervalId);
					blipbalm.intervalId = null;
				}
			}
		},
		
		isInboxCheckingStarted: function() {
			var
				windowManager = this.getWindowManager(),
				enumerator = windowManager.getXULWindowEnumerator(null);
			while (enumerator.hasMoreElements()){
				var
					xulWindow = enumerator.getNext(),
					win = this.getDOMWindow(xulWindow);
				if (blipbalm != null && blipbalm.intervalId != null)
					return true;
			}
			return false;
		},
		
		doNothing: function() {
			this.setStatusBarAttribute('label','disabled');
			this.setStatusBarAttribute('height',1);
			this.setStatusBarAttribute('src',this.iconRead);
		},
		
		start: function() {
			if(this.isInboxCheckingStarted())
				this.stopInboxChecking();
			this.startInboxChecking()
		},
		
		getWindowManager: function() {
			var
				mediatorContractid='@mozilla.org/appshell/window-mediator;1',
				nsIWindowMediator=Components.interfaces.nsIWindowMediator;
			return Components.classes[mediatorContractid].getService(nsIWindowMediator)
		},
		
		getDOMWindow: function(xulWindow){
			xulWindow = xulWindow.QueryInterface(Components.interfaces.nsIXULWindow);
			var
				requestor = xulWindow.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
			return requestor.getInterface(Components.interfaces.nsIDOMWindow)
		},
		
		setStatusBarAttribute: function(attr,value) {
			var
				windowManager=this.getWindowManager(),
				enumerator=windowManager.getXULWindowEnumerator(null);
			while(enumerator.hasMoreElements()){
				var xulWindow=enumerator.getNext();
				xulWindow=xulWindow.QueryInterface(Components.interfaces.nsIXULWindow);
				var myPanel=xulWindow.docShell.contentViewer.DOMDocument.getElementById(this.gwStatusBarId);
				if(myPanel!=null)
					myPanel.setAttribute(attr,value)
			}
		},
		
		addTooltipImage: function(bg,ico){
			var
				vbox=document.createElement('vbox'),
				spacer=document.createElement('spacer'),
				image=document.createElement('image');
			vbox.setAttribute('style','background:url('+bg+') no-repeat center center');
			spacer.setAttribute('style','height:8px');
			image.setAttribute('src',ico);
			image.setAttribute('style','margin:24px 24px 0px 0px');
			vbox.appendChild(spacer);
			vbox.appendChild(image);
			this.tooltipElements[this.tooltipElementCount++]=vbox
		},
		
		addTooltipLabel: function(text,styleClass){
			var
				lbl=document.createElement('label');
			lbl.setAttribute('value',text);
			if(styleClass!=null)
				lbl.setAttribute('style',styleClass);
			this.tooltipLabels[this.tooltipLabelCount++]=lbl
		},
		
		createTooltipElement: function(labels){
			var
				vbox=document.createElement('vbox');
			for(var i=0;i<labels.length;i++)
				vbox.appendChild(labels[i]);
			this.tooltipElements[this.tooltipElementCount++]=vbox
		},
		
		cloneTooltipLabel: function(doc,label){
			var
				clone=doc.createElement('vbox');
			clone=label.cloneNode(true);
			return clone
		},
		
		setStatusBarTooltip: function(labels){
			var
				windowManager=this.getWindowManager(),
				enumerator=windowManager.getXULWindowEnumerator(null);
			while(enumerator.hasMoreElements()){
				var
					xulWindow=enumerator.getNext(),
					win=this.getDOMWindow(xulWindow),
					doc=xulWindow.docShell.contentViewer.DOMDocument;
				if(blipbalm!=null)
					blipbalm.setTooltip(doc,labels);
			}
		},
		
		setTooltip: function(doc,labels){
			var
				tooltip,
				parent;
			if (this.gwStatusBar==null) {
				this.debug('null gw status bar. skip tooltips');
				return
			}
			this.debug('setting tooltip');
			if (this.gwStatusBar.hasAttribute('tooltiptext'))
				this.gwStatusBar.removeAttribute('tooltiptext');
			if (this.gwStatusBar.hasAttribute('tooltip'))
				tooltip=doc.getElementById(this.gwStatusBar.getAttribute('tooltip'));
			if (tooltip == null) {
				parent=doc.getElementById('mainPopupSet');
				tooltip=this.newTooltip(doc,this.gwStatusBar);
				try{
					parent.appendChild(tooltip)
				} catch(e) {
					this.debug('got exception appendChild '+e)
				}
				this.gwStatusBar.setAttribute('tooltip',tooltip.getAttribute('id'))
			}
			while (tooltip.hasChildNodes())
				tooltip.removeChild(tooltip.firstChild);
			var
				hbox=document.createElement('hbox'),
				i;
			for (i = 0; i < labels.length; i++){
				if (labels[i].ownerDocument == doc)
					hbox.appendChild(labels[i]);
				else
					hbox.appendChild(this.cloneTooltipLabel(doc,labels[i]))
			}
			tooltip.appendChild(hbox)
		},
		
		newTooltip: function(doc,gwStatusBar){
			var
				tooltip=doc.createElement('tooltip');
			tooltip.setAttribute('id',gwStatusBar.getAttribute('id')+'-tip');
			tooltip.setAttribute('noautohide',true);
			tooltip.setAttribute('orient','vertical');
			tooltip.setAttribute('crop','none');
			return tooltip
		},
		
		checkNow: function() {
			this.debug('checking inbox now');
			var
				windowManager=this.getWindowManager(),
				enumerator=windowManager.getXULWindowEnumerator(null);
			while(enumerator.hasMoreElements()){
				var
					xulWindow=enumerator.getNext(),
					win=this.getDOMWindow(xulWindow);
				if(blipbalm!=null && blipbalm.intervalId != null) {
					blipbalm.setStatusBarAttribute('label','Loading inbox...');
					blipbalm.setStatusBarAttribute('style','');
					win.setTimeout('blipbalm.readInbox()',1000);  // FIXME magic number?
					return
				}
			}
		},
		
		gotoInbox: function() {
			blipbalm.setStatusBarAttribute('label','');
			gBrowser.selectedTab=gBrowser.addTab(this.inboxURL)
		},
		
		gotoAboutSite: function() {
			this.debug('go to About site');
			gBrowser.selectedTab=gBrowser.addTab(this.aboutURL);
		},
		
		isenabled: function() {
			return this.prefBranch.prefHasUserValue('enabled') ? this.prefBranch.getBoolPref('enabled') : true;
		},
		
		enable: function(value) {
			this.prefBranch.setBoolPref('enabled', value);
			this[!value ? 'doNothing' : 'login']();
		},
		
		getLastUpdated: function() {
			return this.prefBranch.prefHasUserValue('lastUpdated')?new Date(this.toInt(this.prefBranch.getCharPref('lastUpdated'))):null
		},
		
		setLastUpdated: function(date){
			this.prefBranch.setCharPref('lastUpdated',date.getTime())
		},
		
		setLastUpdatedToNow: function() {
			this.setLastUpdated(new Date())
		},
		
		initOption: function() {
			if('@mozilla.org/passwordmanager;1' in Components.classes){
				this.pwdmgrInternal = Components.classes['@mozilla.org/passwordmanager;1'].getService(Components.interfaces.nsIPasswordManagerInternal);
				this.pwdmgr = Components.classes['@mozilla.org/passwordmanager;1'].getService(Components.interfaces.nsIPasswordManager)
			} else {
				this.pwdmgr=Components.classes['@mozilla.org/login-manager;1'].getService(Components.interfaces.nsILoginManager);
			}
		},
		
		/**
		 * prepare editable data for options window
		 */
		optionEditLoad: function() {
			this.debug('loading preferences');
			this.initOption();
			var
				loginData = this.getLoginData();
			if (loginData.username != '')
				document.getElementById('blipbalm-username-field').value = loginData.username;
			if (loginData.password != '')
				document.getElementById('blipbalm-password-field').value = loginData.password;
			this.oldUsername = loginData.username;
			document.getElementById('blipbalm-googleapps-on').checked = this.getAccountType() == 'HOSTED' ? true : false;
			document.getElementById('blipbalm-frequency-field').value = this.getUpdateFrequency() / 60000;
			document.getElementById('blipbalm-httptimeout-field').value = this.getHttpTimeout() / 1000;
		},
		
		/**
		 * Save preferences and login details. If login details have changed,
		 * the notifier needs to be immediately refreshed.
		 */
		optionEditSave: function() {
			// FIXME if user cancels, this is still fired!
			this.debug('saving preferences');
			try {
				var
					username = document.getElementById('blipbalm-username-field').value,
					password = document.getElementById('blipbalm-password-field').value,
					oldAccType = this.getAccountType(),
					accType = document.getElementById('blipbalm-googleapps-on').checked ? 'HOSTED' : 'GOOGLE',
					frequency = document.getElementById('blipbalm-frequency-field').value,
					httpTimeout = document.getElementById('blipbalm-httptimeout-field').value,
					logout = false;
				if (username == '') {
					alert('You must enter an e-mail address');
					return false;
				}
				if (frequency == '' || isNaN(frequency)) {
					alert('You must enter a number for update frequency');
					return false;
				}
				if (httpTimeout == '' || isNaN(httpTimeout)) {
					alert('You must enter a number for HTTP timeout');
					return false;
				}
				if ('@mozilla.org/passwordmanager;1' in Components.classes) {
					// remove existing usernames/passwords stored in Firefox password manager, add the new one
					try {
						// remove existing users/passwords
						for (var i = 0; i < this.passwordSites.length; i++) {
							this.pwdmgr.removeUser(this.passwordSites[i],this.oldUsername);
						}
					} catch(e) {
						this.debug('exception removing password ' + e)
					}
					for (var i = 0; i < this.passwordSites.length; i++) {
						this.pwdmgrInternal.addUserFull(this.passwordSites[i], username, password, this.usernameFormField, this.passwordFormField);
					}
				} else {
					for (var i = 0; i < this.passwordSites.length; i++) {
						var
							logins = this.pwdmgr.findLogins({}, this.passwordSites[i], this.passwordSites[i], null),
							nsLoginInfo = new Components.Constructor('@mozilla.org/login-manager/loginInfo;1', Components.interfaces.nsILoginInfo, 'init'),
							loginInfo = new nsLoginInfo(this.passwordSites[i], this.passwordSites[i], null, username, password, this.usernameFormField, this.passwordFormField),
							j;
						for (j = 0; j < logins.length; j++)
							//if(logins[j].username==this.oldUsername){ // removing all old logins for now
								this.pwdmgr.removeLogin(logins[j]);
							//	break
							//}
						try {
							this.pwdmgr.addLogin(loginInfo);
						} catch(e) {}
					}
				}
				
				if (accType != oldAccType) {
					// account type (Google/hosted) switched, save new settings
					this.prefBranch.setCharPref('accType', accType);
					logout = true;
				}
				
				var
					needToChangeInterval = false,
					frequencyValue = this.toInt(frequency);
				if (this.getUpdateFrequency() != frequencyValue) {
					this.prefBranch.setIntPref('frequency', frequencyValue);
					needToChangeInterval = true;
				}
				this.prefBranch.setIntPref('httpTimeout', this.toInt(httpTimeout));	
				var
					windowManager=this.getWindowManager(),
					enumerator=windowManager.getXULWindowEnumerator(null);
				while (enumerator.hasMoreElements()) {
					// work through all windows, logging out of Google Wave in blipbalm
					this.debug('window found...');
					var
						xulWindow = enumerator.getNext(),
						win = this.getDOMWindow(xulWindow);
					if (blipbalm != null) {
						// found a window we need to deal with
						if (needToChangeInterval && blipbalm.intervalId != null) {
							this.debug('changing interval to ' + this.getUpdateFrequency());
							win.clearInterval(blipbalm.intervalId);
							blipbalm.scheduleInboxChecking(win);
						}
						this.debug('scheduled logout!');
						win.setTimeout('blipbalm.checkWithLogout()', 1000);
						break;
					} else if (blipbalm != null) {
						this.debug('window has blipbalm object');
					}
				}
				return true;
			} catch(e) {
				alert('An unknown error occurred\n' + e);
			}
			return false;
		},
		
		/**
		 * Build a URL using the template provided and the
		 * domain found within the user's email address username
		 * @param url the URL template
		 * @param username the email address containing the hosted domain
		 * @return string the completed URL
		 */
		useDomainFromUsername: function(url, username) {
			var domain = username.replace(/^.+@(.+)$/, "$1");
			return url.replace(/\{domain\}/gi, domain);
		},
		
		getLoginData: function() {
			var
				data = {username: '', password: ''};
			if ('@mozilla.org/passwordmanager;1' in Components.classes){
				for (var i = 0; i < this.passwordSites.length; i++) {
					var
						host = {value: this.passwordSites[i]},
						username = {value: ''},
						password = {value: ''};
					try {
						this.pwdmgrInternal.findPasswordEntry(host.value, '', '', host, username, password);
						this.debug('found account details from the password manager by host ' + this.passwordSites[i]);
						break;
					}
					catch(e) {
						this.debug('no account details retrieved from the password manager by host ' + this.passwordSites[i]);
					}
				}
				data.username = username.value;
				data.password = password.value
			} else {
				for (var i = 0; i < this.passwordSites.length; i++) {
					var
						logins = this.pwdmgr.findLogins({}, this.passwordSites[i], this.passwordSites[i], null);
					if (logins.length > 0){
						// return the first one
						data.username = logins[0].username;
						data.password = logins[0].password;
						break;
					}
				}
			}
			return data;
		},
		
		/**
		 * retrieve account type
		 * GOOGLE for regular Google account
		 * HOSTED for Google Apps account
		 * @return string either GOOGLE or HOSTED depending on account type preference
		 */
		getAccountType: function() {
			try {
				return this.prefBranch.prefHasUserValue('accType')
						&& this.prefBranch.getCharPref('accType') == 'HOSTED' ? 'HOSTED' : 'GOOGLE';
			} catch(e) {
				this.warn(e);
				return this.defaultAccType;
			}
		},
		
		/**
		 * retrieve the update frequency, applying range restrictions
		 * @return int a valid frequency
		 */
		getUpdateFrequency: function() {
			try {
				return this.prefBranch.prefHasUserValue('frequency')
						&& this.prefBranch.getIntPref('frequency') >= this.minFrequency ? 
							this.prefBranch.getIntPref('frequency') * 60000 
							: this.defaultFrequency * 60000;
			} catch(e) {
				this.warn(e);
				return this.defaultFrequency * 60000;
			}
		},
		
		/**
		 * retrieve the HTTP timeout time, applying range restrictions
		 * @return int a valid frequency
		 */
		getHttpTimeout: function() {
			try {
				return this.prefBranch.prefHasUserValue('httpTimeout') ? 
						this.prefBranch.getIntPref('httpTimeout') * 1000
						: this.defaultHttpTimeout * 1000; 
			} catch(e) {
				this.warn(e);
				return this.defaultHttpTimeout * 1000;
			}
		},
		
		callInProgress: function(xmlhttp){
			this.debug('xmlhttp ready state = '+xmlhttp.readyState);
			switch(xmlhttp.readyState){
				case 1:
				case 2:
				case 3:
					return true;
					break;
				default:
					return false;
					break
			}
		},
		
		setReqTimeout: function(url){
			var
				httpTimeout = this.getHttpTimeout();
			this.timeoutId=setTimeout('blipbalm.timeoutFired("'+url+'")',httpTimeout);
			this.debug('set timeout '+httpTimeout+'(ms) id '+this.timeoutId+' for '+url)
		},
		
		timeoutFired: function(url){
			this.debug('timeout fired for '+url);
			if(this.callInProgress(this.httpReq)){
				this.warn('http request to '+url+' timed out');
				this.finishedChecking();
				this.httpReq.abort()
			}
			else
				if(this.httpReq.readyState==4)
					this.finishedChecking()
		},
		
		clearReqTimeout: function() {
			window.clearTimeout(this.timeoutId);
			this.debug('cleared timeout id '+this.timeoutId)
		},
		
		debug: function(msg){
			this.log(this.getDate()+' Blipbalm DEBUG: '+msg+'\n')
		},
		
		warn: function(msg){
			this.log(this.getDate()+' Blipbalm WARNING: '+msg+'\n');
			this.setStatusBarAttribute('tooltiptext',msg)
		},
		
		error: function(msg){
			this.log(this.getDate()+' Blipbalm ERROR: '+msg+'\n');
			this.setStatusBarAttribute('tooltiptext',msg);
			this.setStatusBarAttribute('src', this.iconError);
		},
		
		log: function(msg){
			//window.alert(msg);
			var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
				.getService(Components.interfaces.nsIConsoleService);
			consoleService.logStringMessage(msg);
		},
		
		getDate: function() {
			var
				d=new Date(),
				h=d.getHours()+'',
				m=d.getMinutes()+'',
				s=d.getSeconds()+'';
			if(h.length==1)
				h='0'+h;
			if(m.length==1)
				m='0'+m;
			if(s.length==1)
				s='0'+s;
			return(d.getMonth()+1)+'/'+d.getDate()+'/'+d.getFullYear()+' '+h+':'+m+':'+s
		},
		
		toInt: function(s){
			return s[0]=='0'?parseInt(s.substring(1,s.length)):parseInt(s)
		}
	}
}();

window.addEventListener('load', blipbalm.load, false);
window.addEventListener('unload', blipbalm.unload, false);